#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/select.h>
#include <sys/time.h>
#include "uart_interface.h"

#define PROGRAM_NAME "uart_interface"

int uart_open(int fd, const char *pathname)
{
    fd = open(pathname, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (fd == -1)
        errmsg_die("Open UART failed!");

    return fd;
}

/**
 * @brief 配置UART参数
 *
 * @param fd 文件描述符
 * @param baude 波特率
 * @param c_flow 数据流控制
 * @param bits 数据位
 * @param parity 校验位
 * @param stop 停止位
 * @return int
 */
int uart_set(int fd, int baude, int c_flow, int bits, char parity, int stop)
{
    struct termios options;

    /*获取终端属性*/
    if (tcgetattr(fd, &options) < 0)
        errmsg_die("tcgetattr error");

    /* 波特率 */
    switch (baude)
    {
    case 4800:
        cfsetispeed(&options, B4800);
        cfsetospeed(&options, B4800);
        break;
    case 9600:
        cfsetispeed(&options, B9600);
        cfsetospeed(&options, B9600);
        break;
    case 19200:
        cfsetispeed(&options, B19200);
        cfsetospeed(&options, B19200);
        break;
    case 38400:
        cfsetispeed(&options, B38400);
        cfsetospeed(&options, B38400);
        break;
    case 115200:
        cfsetispeed(&options, B115200);
        cfsetospeed(&options, B115200);
        break;
    default:
        errmsg_die("Unkown baude!\n");
    }

    /* 控制模式 */
    options.c_cflag |= CLOCAL; // 保证程序不占用串口
    options.c_cflag |= CREAD;  // 保证程序可以从串口中读取数据

    /*设置数据流控制*/
    switch (c_flow)
    {
    case 0: // 不进行流控制
        options.c_cflag &= ~CRTSCTS;
        break;
    case 1: // 进行硬件流控制
        options.c_cflag |= CRTSCTS;
        break;
    case 2: // 进行软件流控制
        options.c_cflag |= IXON | IXOFF | IXANY;
        break;
    default:
        errmsg_die("Unkown c_flow!\n");
    }

    /* 数据位 */
    switch (bits)
    {
    case 5:
        options.c_cflag &= ~CSIZE; // 屏蔽其它标志位
        options.c_cflag |= CS5;
        break;
    case 6:
        options.c_cflag &= ~CSIZE; // 屏蔽其它标志位
        options.c_cflag |= CS6;
        break;
    case 7:
        options.c_cflag &= ~CSIZE; // 屏蔽其它标志位
        options.c_cflag |= CS7;
        break;
    case 8:
        options.c_cflag &= ~CSIZE; // 屏蔽其它标志位
        options.c_cflag |= CS8;
        break;
    default:
        errmsg_die("Unkown bits!\n");
    }

    /* 校验位 */
    switch (parity)
    {
    case 'n':
    case 'N':
        /* 无奇偶校验位 */
        options.c_cflag &= ~PARENB; // PARENB：产生奇偶位，执行奇偶校验
        options.c_cflag &= ~INPCK;  // INPCK：使奇偶校验起作用
        break;
    case 's':
    case 'S':
        /* 设为空格,即停止位为2位 */
        options.c_cflag &= ~PARENB; // PARENB：产生奇偶位，执行奇偶校验
        options.c_cflag &= ~CSTOPB; // CSTOPB：使用两位停止位
        break;
    case 'o':
    case 'O':
        /*设置奇校验*/
        options.c_cflag |= PARENB; // PARENB：产生奇偶位，执行奇偶校验
        options.c_cflag |= PARODD; // PARODD：若设置则为奇校验,否则为偶校验
        options.c_cflag |= INPCK;  // INPCK：使奇偶校验起作用
        options.c_cflag |= ISTRIP; // ISTRIP：若设置则有效输入数字被剥离7个字节，否则保留全部8位
        break;
    case 'e':
    case 'E':
        /*设置偶校验*/
        options.c_cflag |= PARENB;  // PARENB：产生奇偶位，执行奇偶校验
        options.c_cflag &= ~PARODD; // PARODD：若设置则为奇校验,否则为偶校验
        options.c_cflag |= INPCK;   // INPCK：使奇偶校验起作用
        options.c_cflag |= ISTRIP; // ISTRIP：若设置则有效输入数字被剥离7个字节，否则保留全部8位
        break;
    default:
        errmsg_die("Unkown parity!\n");
    }

    /* 停止位 */
    switch (stop)
    {
    case 1:
        options.c_cflag &= ~CSTOPB; // CSTOPB：使用两位停止位
        break;
    case 2:
        options.c_cflag |= CSTOPB; // CSTOPB：使用两位停止位
        break;
    default:
        errmsg_die("Unkown stop!\n");
    }

    /* 设置输出模式为原始输出 */
    options.c_oflag &= ~OPOST; // OPOST：若设置则按定义的输出处理，否则所有c_oflag失效

    /* 设置本地模式为原始模式 */
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    /*
     * ICANON：行编辑模式
     * ECHO：输入字符的本地回显
     * ECHOE：可视退格
     * ISIG：Ctrl+C, Ctrl+\, Ctrl+Z
     */

    /* 设置等待时间和最小接受字符 */
    options.c_cc[VTIME] = 0; // 可以在select中设置
    options.c_cc[VMIN] = 1;  // 最少读取一个字符

    // 清除接收缓冲区
    tcflush(fd, TCIFLUSH);

    /* 激活配置 */
    if (tcsetattr(fd, TCSANOW, &options) < 0)
        errmsg_die("tcsetattr failed");

    return 0;
}

int uart_read(int fd, char *r_buf, size_t len)
{
    ssize_t cnt = 0;
    fd_set rfds;
    struct timeval time;
    int ret;

    /* 将文件描述符加入读描述符集合 */
    FD_ZERO(&rfds);
    FD_SET(fd, &rfds);

    /* 设置超时为5s */
    time.tv_sec = 5;
    time.tv_usec = 0;

    /* 实现串口的多路I/O */
    ret = select(fd + 1, &rfds, NULL, NULL, &time);
    switch (ret)
    {
    case -1:
        errmsg_die("select error!\n");
    case 0:
        errmsg_die("time over!\n");
    default:
        cnt = read(fd, r_buf, len);
        if (cnt == -1)
            errmsg_die("read error!\n");
        return cnt;
    }
}

int uart_write(int fd, const char *w_buf, size_t len)
{
    ssize_t cnt = 0;

    cnt = write(fd, w_buf, len);
    if (cnt == -1)
        errmsg_die("write error!\n");

    return cnt;
}

int uart_close(int fd)
{
    close(fd);
    return 0;
}
